﻿using DesertKharonIrcPlugin.Emotes.Twitch.Exceptions;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.Serialization;
//using System.Runtime.Serialization.Json;
using System.Text;

namespace DesertKharonIrcPlugin.Emotes.Twitch
{
    public static class TwitchEmoteParser
    {
        private static string _defaultStandardEmotesUrl = "http://twitchemotes.com/";
        private static string _defaultGlobalJsonEmotesUrl = "http://twitchemotes.com/global.json";
        private static string _defaultSubscriberEmotesUrl = "http://twitchemotes.com/subscriber.json";
        private static string _defaultBetterTtvEmotesUrl = "http://nightdev.com/betterttv/faces.php";

        private static List<TwitchEmote> _cachedStandardEmoteList = new List<TwitchEmote>();
        private static List<TwitchEmote> _cachedGlobalEmoteList = new List<TwitchEmote>();
        private static List<TwitchEmote> _cachedSubscriberEmoteList = new List<TwitchEmote>();
        private static List<TwitchEmote> _cachedBetterTtvEmoteList = new List<TwitchEmote>();

        public static string DefaultStandardEmotesUrl
        {
            get { return _defaultStandardEmotesUrl; }
            set { _defaultStandardEmotesUrl = value; }
        }

        public static string DefaultGlobalJsonEmotesUrl
        {
            get { return _defaultGlobalJsonEmotesUrl; }
            set { _defaultGlobalJsonEmotesUrl = value; }
        }

        public static string DefaultSubscriberEmotesUrl
        {
            get { return _defaultSubscriberEmotesUrl; }
            set { _defaultSubscriberEmotesUrl = value; }
        }

        public static string DefaultBetterTtvEmotesUrl
        {
            get { return _defaultBetterTtvEmotesUrl; }
            set { _defaultBetterTtvEmotesUrl = value; }
        }

        public static List<TwitchEmote> ParseStandard(bool cached = true)
        {
            if (_cachedStandardEmoteList.Count == 0 || !cached)
                _cachedStandardEmoteList = ParseStandard(_defaultStandardEmotesUrl);

            return _cachedStandardEmoteList.ToList();
        }

        private static List<TwitchEmote> ParseStandard(string url)
        {
            if (String.IsNullOrWhiteSpace(url))
                throw new ArgumentNullException("url");

            string downloadContent = DownloadString(url);

            string rawEntryContent = downloadContent.Extract("<h2>Robot Emotes</h2>", "<h2>Face Emotes</h2>");
            if (String.IsNullOrWhiteSpace(rawEntryContent))
                throw new TwitchEmoteParserDownloadInvalidDataException(url, "Could not extract content between \"<h2>Robot Emotes</h2>\" and \"<h2>Face Emotes</h2>\".");

            List<string> rawEntryList = rawEntryContent.ExtractList("<div class=\"span2\"><center><img src=\"", "</center><br/></div>", StringSplitOptions.RemoveEmptyEntries);

            if (rawEntryList.Count == 0)
                throw new TwitchEmoteParserDownloadInvalidDataException(url, "No data found between \"<div class=\"span2\"><center><img src=\" and \"</center><br/></div>\".");

            List<TwitchEmote> emoteList = rawEntryList
                .Where(p => p.StartsWith("http://"))
                .Select(p => p.Replace("&gt;", ">").Replace("&lt;", "<").Split(new string[] { "\"/><br/>" }, StringSplitOptions.None))
                .Where(p => p.Length == 2)
                .Select(p => new TwitchEmote 
                {
                    Channel = "standardEmotes",
                    Name = p[1],
                    Description = "none",
                    Url = p[0]
                })
                .ToList();

            if (emoteList.Count == 0)
                throw new TwitchEmoteParserDownloadInvalidDataException(url, "No emote entries found.");

            return emoteList;
        }

        public static List<TwitchEmote> ParseBetterTtv(bool cached = true)
        {
            if (_cachedBetterTtvEmoteList.Count == 0 || !cached)
                _cachedBetterTtvEmoteList = ParseBetterTtv(_defaultBetterTtvEmotesUrl);

            return _cachedBetterTtvEmoteList.ToList();
        }

        private static List<TwitchEmote> ParseBetterTtv(string url)
        {
            List<TwitchEmote> resultList = new List<TwitchEmote>();

            string downloadContent = DownloadString(url);

            var rawEntryContent = downloadContent.Extract("[", "]");
            if (String.IsNullOrWhiteSpace(rawEntryContent))
                throw new TwitchEmoteParserDownloadInvalidDataException(url, "Downloaded content is empty between '[' and ']'.");

            List<string> rawEntryList = rawEntryContent.ExtractList("{ ", " }", StringSplitOptions.RemoveEmptyEntries);
            if (rawEntryList == null || rawEntryList.Count == 0)
                throw new TwitchEmoteParserDownloadInvalidDataException(url, "No entries found between '[' and ']'.");

            for (int i = 0; i < rawEntryList.Count; i++)
            {
                string rawEntry = rawEntryList[i];

                string entryUrl = ExtractUrlFromBetterTtvEntry(url, rawEntry);
                List<string> entryKeyList = ExtractKeysFromBetterTtvEntry(url, rawEntry);

                if (entryKeyList.Count > 1)
                {
                    foreach (var key in entryKeyList)
                    {
                        resultList.Add(new TwitchEmote
                        {
                            Channel = "betterTTV",
                            Name = key,
                            Description = "none",
                            Url = entryUrl
                        });
                    }
                }
                else
                {
                    resultList.Add(new TwitchEmote
                    {
                        Channel = "betterTTV",
                        Name = entryKeyList[0],
                        Description = "none",
                        Url = entryUrl
                    });
                }
            }

            return resultList;
        }

        private static string ExtractUrlFromBetterTtvEntry(string url, string rawEntry)
        {
            string entryUrl = rawEntry.Extract("url: ", ",", EndTokenNotFoundAction.StartTokenToStringEnd);
            if (String.IsNullOrWhiteSpace(entryUrl))
                throw new TwitchEmoteParserDownloadInvalidDataException(url, String.Format("Entry '{0}' has no Url.", rawEntry));

            entryUrl = entryUrl.Extract("\"", "\"");
            if (String.IsNullOrWhiteSpace(entryUrl))
                throw new TwitchEmoteParserDownloadInvalidDataException(url, String.Format("Entry '{0}' has no Url.", rawEntry));

            entryUrl = "http:" + entryUrl;
            return entryUrl;
        }

        private static List<string> ExtractKeysFromBetterTtvEntry(string url, string rawEntry)
        {
            string entryKeys = rawEntry.Extract("regex: ", ",", EndTokenNotFoundAction.StartTokenToStringEnd);
            if (String.IsNullOrWhiteSpace(entryKeys))
                throw new TwitchEmoteParserDownloadInvalidDataException(url, String.Format("Entry '{0}' has no regex keys.", rawEntry));

            entryKeys = entryKeys.Extract("\"", "\"");
            if (String.IsNullOrWhiteSpace(entryKeys))
                throw new TwitchEmoteParserDownloadInvalidDataException(url, String.Format("Entry '{0}' has no regex keys.", rawEntry));

            entryKeys = entryKeys.Replace("\\", "");
            if (String.IsNullOrWhiteSpace(entryKeys))
                throw new TwitchEmoteParserDownloadInvalidDataException(url, String.Format("Entry '{0}' has no regex keys.", rawEntry));

            if (entryKeys.Contains("|") && entryKeys.StartsWith("("))
            {
                entryKeys = entryKeys.Extract("(", ")");
                if (String.IsNullOrWhiteSpace(entryKeys))
                    throw new TwitchEmoteParserDownloadInvalidDataException(url, String.Format("Entry '{0}' has invalid regex keys.", rawEntry));
            }

            List<string> entryKeyList = entryKeys.Replace("\\", "").Split('|').ToList();
            if (entryKeyList.Count == 0)
                throw new TwitchEmoteParserDownloadInvalidDataException(url, String.Format("Entry '{0}' has no regex keys.", rawEntry));

            return entryKeyList;
        }

        public static List<TwitchEmote> ParseGlobal(bool cached = true)
        {
            if (_cachedGlobalEmoteList.Count == 0 || !cached)
                _cachedGlobalEmoteList = ParseGlobal(_defaultGlobalJsonEmotesUrl);

            return _cachedGlobalEmoteList.ToList();
        }

        private static List<TwitchEmote> ParseGlobal(String url)
        {
            List<TwitchEmote> resultList = new List<TwitchEmote>();

            string downloadContent = DownloadString(url);

            dynamic objs = JObject.Parse(downloadContent);
            foreach (var o in objs)
            {
                resultList.Add(new TwitchEmote
                { 
                    Channel = "default",
                    Name = o.Name, 
                    Url = o.Value.url, 
                    Description = o.Value.description 
                });
            }

            return resultList;
        }

        public static List<TwitchEmote> ParseSubscriber(bool cached = true)
        {
            if (_cachedSubscriberEmoteList.Count == 0 || !cached)
                _cachedSubscriberEmoteList = ParseSubscriber(_defaultSubscriberEmotesUrl);

            return _cachedSubscriberEmoteList.ToList();
        }

        private static List<TwitchEmote> ParseSubscriber(String url)
        {
            List<TwitchEmote> resultList = new List<TwitchEmote>();

            string downloadContent = DownloadString(url);

            dynamic channels = JObject.Parse(downloadContent);
            foreach (var objs in channels)
            {
                foreach (var o in objs.Value)
                {
                    if (o.Name != "emotes")
                        continue;

                    foreach (var pair in o.Value)
                    {
                        resultList.Add(new TwitchEmote
                        {
                            Channel = objs.Name,
                            Name = pair.Name,
                            Url = pair.Value
                        });
                    }
                }
            }

            return resultList;
        }

        public static List<TwitchEmote> ParseAll()
        {
            List<TwitchEmote> emoteList = new List<TwitchEmote>(10000);
            emoteList.AddRange(ParseStandard());
            emoteList.AddRange(ParseGlobal());
            emoteList.AddRange(ParseSubscriber());
            emoteList.AddRange(ParseBetterTtv());

            return emoteList;
        }

        private static string DownloadString(string url)
        {
            string result = null;
            try
            {
                using (var webClient = new WebClient())
                {
                    result = webClient.DownloadString(url);
                }
            }
            catch (Exception ex)
            {
                throw new TwitchEmoteParserDownloadException(url, ex);
            }

            if (String.IsNullOrWhiteSpace(result))
                throw new TwitchEmoteParserDownloadEmptyException(url);

            return result;
        }
    }
}
